<?php

namespace JyUtils\Guard;

use JyUtils\Redis\Redis;
use JyUtils\Request\Request;

trait GuardIp
{
  /**
   * 开始守护IP请求
   */
  private static function startGuardIp()
  {
    $client_ip = Request::getIp();
    
    $value = Redis::hget(self::$table_request_ip, $client_ip);
    
    if (!Redis::hexists(self::$table_request_ip, $client_ip)) {
      $value = self::getIpDefalutValue($client_ip);
    } else {
      $value = self::incIpValue($client_ip, $value);
    }
    Redis::hset(self::$table_request_ip, $client_ip, $value);
    
    // 处理ip请求的拦载，并加入到黑名单中
    self::ipIsOverload($value['value']);
    
    // IP是否在黑名单
    self::ipIsBlack($client_ip, $value['value']);
  }
  
  /**
   * 添加ip黑名单
   *
   * @param string $client_ip Ip值
   * @param int    $expire    失效时间, 留空为一年，-1为永不失效
   */
  public static function addBlackIp($client_ip = null, $expire = null)
  {
    $client_ip = $client_ip ?: Request::getIp();
    if ($expire) {
      $expire = strlen($expire) == 10 ? $expire : time() + $expire;
    } else if ($expire == -1) {
      $expire = 0;
    } else {
      $expire = time() + 31536000;
    }
    Redis::hset(self::$table_black_ip, $client_ip, [
      'value'  => $client_ip,
      'expire' => $expire,
    ]);
  }
  
  /**
   * 取ip黑名单列表
   *
   * @return array
   */
  public static function getBlackIpList()
  {
    $res  = Redis::hgetall(self::$table_black_ip);
    $list = [];
    foreach ($res as $v) {
      $list[] = json_decode($v, true);
    }
    return $list;
  }
  
  /**
   * 取当前请求的列表
   *
   * @return array
   */
  public static function getRequsetIpList()
  {
    $res  = Redis::hgetall(self::$table_request_ip);
    $list = [];
    foreach ($res as $ip => $v) {
      $temp = json_decode($v, true);
      $temp['ip'] = $ip;
      $list[] = $temp;
    }
    return $list;
  }
  
  /**
   * ip是否在黑名单中
   *
   * @param string $client_ip     ip值，留空将取当前请求的ip
   * @param int    $request_count 当前IP的请求累计数(在指定时间内)
   */
  private static function ipIsBlack($client_ip = null, $request_count = 0)
  {
    $client_ip = $client_ip ?: Request::getIp();
    // 未在黑名单中
    if (!$black_ip = Redis::hget(self::$table_black_ip, $client_ip)) {
      return false;
    }
    
    if ($black_ip = json_decode($black_ip, true)) {
      // 是否失效
      if ($black_ip['expire'] < time()) {
        return Redis::hdel(self::$table_black_ip, $client_ip);
        
        // 累计值大于严格
      } else if ($request_count > self::$config['strict_ip_count']) {
        // 是否拦截
        if (self::$config['strict_ip_black_intercept']) {
          fail('请求异常，异常码(2001)');
        }
        
        // 轻微
      } else {
        // 是否拦截
        if (self::$config['light_ip_black_intercept']) {
          fail('请求异常，异常码(2000)');
        }
      }
    }
  }
  
  /**
   * 取IP默认值
   *
   * @return mixed
   */
  private static function getIpDefalutValue($client_ip)
  {
    return [
      'value'  => 1,
      'expire' => time() + self::$config['request_duration'],
    ];
  }
  
  /**
   * 累计请求次数
   *
   * @param string $client_ip 客户IP
   * @param array  $oldValue  原值
   * @return array|mixed
   */
  private static function incIpValue($client_ip, $oldValue)
  {
    if ($oldValue = json_decode($oldValue, true)) {
      // 是否失效
      if ($oldValue['expire'] < time()) {
        return self::getIpDefalutValue($client_ip);
      }
      $oldValue['value']++;
      return $oldValue;
    }
    return self::getIpDefalutValue($client_ip);
  }
  
  /**
   * ip请求是否过载
   *
   * @param int $request_count 当前IP的请求累计数(在指定时间内)
   */
  private static function ipIsOverload($request_count)
  {
    // 严重
    if ($request_count > self::$config['strict_ip_count']) {
      // 拉入黑名单，0=不拉黑
      if (self::$config['strict_ip_black'] != 0) {
        self::addBlackIp(Request::getIp(), self::$config['strict_ip_black']);
      }
      
      // 请求限制，拦截
      if (self::$config['strict_ip_intercept']) {
        fail('请求频率过高，请稍后再试，异常码(2011)。');
        
        // 请求限制，只记录不拦截
      } else {
        return;
      }
      
      // 轻微
    } else if ($request_count > self::$config['light_ip_count']) {
      // 拉入黑名单，0=不拉黑
      if (self::$config['light_ip_black'] != 0) {
        self::addBlackIp(Request::getIp(), self::$config['light_ip_black']);
      }
      
      // 请求限制，拦截
      if (self::$config['light_ip_intercept']) {
        fail('请求过于频繁，请稍后再试，异常码(2010)。');
        
        // 请求限制，只记录不拦截
      } else {
        return;
      }
    }
  }
}
